/*
   Copyright 2011 James Bowling

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package com.armedia.dctm.constraints;

import static com.armedia.dctm.constraints.ConstraintUtils.getAttrIndex;
import static com.armedia.dctm.constraints.ConstraintUtils.isNull;

import org.apache.log4j.Logger;

import com.documentum.com.DfClientX;
import com.documentum.com.IDfClientX;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfPersistentObject;
import com.documentum.fc.client.IDfQuery;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfType;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLogger;
import com.documentum.fc.common.IDfAttr;
import com.documentum.fc.common.IDfId;

public class NotNullConstraintImpl implements Constraint {
	
	private static final ConstraintType CONSTRAINT_TYPE = ConstraintType.NOT_NULL;
	
	private static final Logger LOGGER = DfLogger.getLogger(NotNullConstraintImpl.class);

	private String typeName;
	
	private String attributeName;
	
	public NotNullConstraintImpl(String typeName, String attributeName) {
		super();
		if (typeName == null || typeName.length() == 0) {
			throw new IllegalArgumentException("typeName argument must not be null.");
		}
		if (attributeName == null || attributeName.length() == 0) {
			throw new IllegalArgumentException("singleTimeAttributeName argument must not be null.");
		}
		this.typeName = typeName;
		this.attributeName = attributeName;
	}	

	public void check(IDfPersistentObject persistentObject) throws DfException {
		final IDfId objectId = persistentObject.getObjectId();
		LOGGER.debug(
			String.format("checking not null constraints for [%s.%s] id ['%s'].", 
			this.typeName, this.attributeName, objectId.getId())
		);		
		final IDfSession session = persistentObject.getSession();
		final IDfType type = session.getType(typeName);
		final int attrIndex = getAttrIndex(type, this.attributeName);
		final IDfAttr attr = persistentObject.getAttr(attrIndex);
		boolean isNull = false;		
		if (attr.isRepeating()) {
			final int valueCount = persistentObject.getValueCount(attr.getName());
			for (int i = 0; i < valueCount; i++) {
				isNull = isNull(persistentObject, attr, i);
				if (isNull) {
					String msgPattern = "[%s] integrity constraint violated for attribute [%s.%s] of object [%s] at index [%s].";
					String msg = String.format(msgPattern, CONSTRAINT_TYPE, this.typeName, this.attributeName, objectId.getId(), i);
					throw new NotNullConstraintException(this.typeName, this.attributeName, msg);
				}				
			}
		} else {
			isNull = isNull(persistentObject, attr);
			if (isNull) {
				String msgPattern = "[%s] integrity constraint violated for attribute [%s.%s] of object [%s].";
				String msg = String.format(msgPattern, CONSTRAINT_TYPE, this.typeName, this.attributeName, objectId.getId());
				throw new NotNullConstraintException(this.typeName, this.attributeName, msg);				
			}
		}
	}

	public void checkAll(IDfSession session) throws DfException {
		LOGGER.debug(String.format("[%s] constraint checking all [%s.%s] for null values.", CONSTRAINT_TYPE, this.typeName, this.attributeName));
		final IDfType type = session.getType(this.typeName);		
		final int attrIndex = getAttrIndex(type, this.attributeName);
		final IDfAttr attr = type.getTypeAttr(attrIndex);
		final int dataType = attr.getDataType();
		String nullCriteria = null;
		switch (dataType) {
		case IDfType.DF_STRING:
			nullCriteria = "is nullstring";
			break;
		case IDfType.DF_TIME:
			nullCriteria = "is nulldate";
			break;
		case IDfType.DF_ID:
			nullCriteria = "= '0000000000000000'";
			break;
		default:
			String msgPattern = "Unsupported data type for not-null constraint for [%s.%s].";
			String msg = String.format(msgPattern, typeName, attributeName);
			throw new DfException(msg);
		}
		if (nullCriteria != null) {
			String dqlPattern = "select count(*) from %s where %s %s %s";
			String any = attr.isRepeating() ? "any" : "";
			String dql = String.format(dqlPattern, typeName, attributeName, any, nullCriteria);
			int count = executeSelectCountQuery(session, dql);
			if (count > 0) {
				String msgPattern = "[%s] integrity constraint violated for [%s.%s], (%s object(s) currently in violation).";
				String msg = String.format(msgPattern, CONSTRAINT_TYPE, this.typeName, this.attributeName, count);
				throw new NotNullConstraintException(this.typeName, this.attributeName, msg);
			}
		}
	}
	
	private int executeSelectCountQuery(IDfSession session, String dql) throws DfException {
		LOGGER.debug(dql);
		IDfClientX client = new DfClientX();
		IDfQuery q = client.getQuery();
		q.setDQL(dql);
		int count = 0;
		IDfCollection coll = null;
		try {
			coll = q.execute(session, IDfQuery.READ_QUERY);
			coll.next();
			count = coll.getInt(coll.getAttr(0).getName());
		} finally {
			if (coll != null) {
				coll.close();
				coll = null;
			}
		}
		return count;
	}

}
